package com.smartleanx.module.hrm.controller.admin.payroll;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.smartleanx.framework.common.core.KeyValue;
import com.smartleanx.framework.common.enums.BizTypeEnum;
import com.smartleanx.framework.common.pojo.CommonResult;
import com.smartleanx.framework.common.pojo.PageResult;
import com.smartleanx.framework.common.util.collection.CollectionUtils;
import com.smartleanx.framework.common.util.object.BeanUtils;
import com.smartleanx.framework.excel.core.handler.SelectSheetWriteHandler;
import com.smartleanx.module.hrm.controller.admin.payroll.vo.*;
import com.smartleanx.module.hrm.controller.admin.payroll.vo.inner.PayrollEmployeeBaseVO;
import com.smartleanx.module.hrm.dal.dataobject.employee.EmployeeDO;
import com.smartleanx.module.hrm.dal.dataobject.payroll.PayrollDO;
import com.smartleanx.module.hrm.dal.dataobject.payroll.PayrollDetailDO;
import com.smartleanx.module.hrm.eums.employee.EmployeeStatusEnum;
import com.smartleanx.module.hrm.eums.payroll.PayrollSendStatusEnum;
import com.smartleanx.module.hrm.eums.payroll.PayrollStateEnum;
import com.smartleanx.module.hrm.framework.excel.EasyExcelListener;
import com.smartleanx.module.hrm.intergration.CustomFieldServiceProvider;
import com.smartleanx.module.hrm.intergration.DeptServiceProvider;
import com.smartleanx.module.hrm.intergration.PostLevelServiceProvider;
import com.smartleanx.module.hrm.intergration.PostServiceProvider;
import com.smartleanx.module.hrm.service.employee.EmployeeService;
import com.smartleanx.module.hrm.service.payroll.PayrollDetailService;
import com.smartleanx.module.hrm.service.payroll.PayrollService;
import com.smartleanx.module.system.api.customfield.dto.CustomFieldReqDTO;
import com.smartleanx.module.system.api.customfield.dto.CustomFieldRespDTO;
import com.smartleanx.module.system.api.customfield.dto.CustomFieldSaveDTO;
import com.smartleanx.module.system.api.dept.dto.DeptRespDTO;
import com.smartleanx.module.system.api.dept.dto.PostLevelRespDTO;
import com.smartleanx.module.system.api.dept.dto.PostRespDTO;
import com.smartleanx.module.system.enums.customfield.CustomFieldLabelEnum;
import com.smartleanx.module.system.enums.customfield.CustomFieldTypeEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.hibernate.validator.constraints.Range;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.Positive;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

import static com.smartleanx.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.smartleanx.framework.common.pojo.CommonResult.success;
import static com.smartleanx.module.hrm.enums.ErrorCodeConstants.*;

@Tag(name = "管理后台 - 薪资月份")
@RestController
@RequestMapping("/hrm/payroll")
@Validated
public class PayrollController {

    @Resource
    private PayrollService payrollService;

    @Resource
    private CustomFieldServiceProvider customFieldServiceProvider;

    @Resource
    private PayrollDetailService payrollDetailService;

    @Resource
    private EmployeeService employeeService;

    @Resource
    private DeptServiceProvider deptServiceProvider;

    @Resource
    private PostServiceProvider postServiceProvider;

    @Resource
    private PostLevelServiceProvider postLevelServiceProvider;


    @GetMapping("/get")
    @Operation(summary = "获得薪资月份以及详情(薪资管理列表、工资条总览)")
    @PreAuthorize("@ss.hasPermission('hrm:payroll-detail:query')")
    public CommonResult<PayrollRespVO> getPayroll() {
        PayrollDO payroll = getCurrentPayroll();
        if(Objects.isNull(payroll)){
            return success(null);
        }
        List<PayrollDetailDO> detailList = payrollDetailService.getDetailListByPayrollId(payroll.getId());
        PayrollRespVO respVO = BeanUtils.toBean(payroll, PayrollRespVO.class);
        respVO.setSentCount(CollUtil.count(detailList, detail -> PayrollSendStatusEnum.SENT.getValue().equals(detail.getSendStatus())));
        respVO.setConfirmedCount(CollUtil.count(detailList, detail -> Boolean.TRUE.equals(detail.getConfirmStatus())));
        return success(respVO);
    }

    @GetMapping("/compare-employee")
    @Operation(summary = "人数对比")
    @PreAuthorize("@ss.hasPermission('hrm:payroll:compare-employee')")
    public CommonResult<PayrollEmployeeRespVO> compareEmployee() {
        PayrollDO payroll = getCurrentPayroll();
        if(Objects.isNull(payroll)){
            return success(null);
        }
        List<EmployeeDO> employeeList = employeeService.getEmployeePayee(payroll.getYear(), payroll.getMonth());
        Set<Long> set = CollectionUtils.convertSet(payrollDetailService.getDetailListByPayrollId(payroll.getId()), PayrollDetailDO::getEmployeeId);
        employeeList.removeIf(employee -> set.remove(employee.getId()));
        PayrollEmployeeRespVO respVO = PayrollEmployeeRespVO.builder().unpaidCount(employeeList.size()).build();
        employeeList.addAll(employeeService.getEmployeeList(set));
        // 准备
        Map<Long, String> deptMap = CollectionUtils.convertMap(deptServiceProvider.getDeptList(CollectionUtils.convertSet(employeeList, EmployeeDO::getDeptId)), DeptRespDTO::getId, DeptRespDTO::getName);
        Map<Long, String> postMap = CollectionUtils.convertMap(postServiceProvider.getPostList(CollectionUtils.convertSet(employeeList, EmployeeDO::getPostId)), PostRespDTO::getId, PostRespDTO::getName);
        // 拼装
        respVO.setDatas(CollectionUtils.convertList(employeeList, employee -> {
            PayrollEmployeeBaseVO baseVO = BeanUtils.toBean(employee, PayrollEmployeeBaseVO.class);
            if(deptMap.containsKey(employee.getDeptId())){
                baseVO.setDeptName(deptMap.get(baseVO.getDeptId()));
            }
            if(postMap.containsKey(employee.getPostId())){
                baseVO.setPostName(postMap.get(baseVO.getPostId()));
            }
            return baseVO;
        }));
        return success(respVO);
    }

    @PutMapping("/archive")
    @Operation(summary = "归档")
    @PreAuthorize("@ss.hasPermission('hrm:payroll:archive')")
    public CommonResult<Boolean> archive() {
        PayrollDO payroll = getCurrentPayroll();
        if(Objects.isNull(payroll)){
            return success(false);
        }
        payrollService.archive(payroll.getId());
        return success(true);
    }

    /**
     * 获取当前正在操作的薪资月份
     */
    private PayrollDO getCurrentPayroll() {
        return payrollService.getPayrollByState(CollUtil.newArrayList(PayrollStateEnum.INITIAL.getValue(), PayrollStateEnum.ARCHIVING.getValue()));
    }

    @GetMapping("/page")
    @Operation(summary = "获得薪资月份分页（薪资记录)")
    @PreAuthorize("@ss.hasPermission('hrm:payroll:query')")
    public CommonResult<PageResult<PayrollPageRespVO>> getPayrollPage(@Valid PayrollPageReqVO pageReqVO) {
        PageResult<PayrollDO> pageResult = payrollService.getPayrollPage(pageReqVO);
        return success(BeanUtils.toBean(pageResult, PayrollPageRespVO.class));
    }

    @PutMapping("/update-field")
    @Operation(summary = "更新薪资项")
    @PreAuthorize("@ss.hasPermission('hrm:payroll:update-field')")
    public CommonResult<Boolean> replaceCustomField(@Valid @RequestBody CustomFieldSaveDTO updateReqVO) {
        PayrollDO payroll = getCurrentPayroll();
        if(Objects.nonNull(payroll)){
            throw exception(PAYROLL_FIELD_USED);
        }
        return success(customFieldServiceProvider.crudFiled(updateReqVO));
    }

    @GetMapping("/get-import-template")
    @Operation(summary = "获得工资导入模板")
//    @PreAuthorize("@ss.hasPermission('hrm:payroll:import')")
    public void importTemplate(@RequestParam("year") @Positive(message = "年份格式非法") Integer year,
                               @RequestParam("month") @Range(min = 1, max = 12, message = "月份格式非法") Integer month,
                               HttpServletResponse response) throws IOException {
        List<CustomFieldRespDTO> fieldList = customFieldServiceProvider.getFieldList(
                CustomFieldReqDTO.builder().bizType(BizTypeEnum.PAYROLL.getValue())
                        .labels(CollUtil.newArrayList(CustomFieldLabelEnum.PAYROLL_EMPLOYEE_INFO.getValue(),
                                CustomFieldLabelEnum.PAYROLL_DETAIL_INFO.getValue())).build());

        List<List<String>> head = new ArrayList<>();
        List<KeyValue<Integer, List<String>>> selectMap = new ArrayList<>();
        // 构建
        int num = 0; // 列号
        for (CustomFieldRespDTO field : fieldList) {
            String name = field.getName();
            if (!field.getIsNull() || (CustomFieldLabelEnum.PAYROLL_EMPLOYEE_INFO.getValue().equals(field.getLabel()) &&
                    !"职级".equals(field.getName()))) {
                //标记必填
                name = "*" + name;
            }
            // 构建下拉选项
            if(JSONUtil.isTypeJSONArray(field.getOptions())){
                JSONArray array = new JSONArray(field.getOptions());
                List<String> list = new ArrayList<>();
                for (int i = 0; i < array.size(); i++) {
                    list.add(array.getJSONObject(i).getStr("label"));
                }
                selectMap.add(new KeyValue<>(num, list));
            }else if(CustomFieldTypeEnum.SWITCH.getValue().equals(field.getType())){
                selectMap.add(new KeyValue<>(num, CollUtil.newArrayList("开启", "关闭")));
            }
            head.add(Collections.singletonList(name));
            num++;
        }
        List<EmployeeDO> employeeList = employeeService.getEmployeePayee(year, month);

        Map<Long, String> deptMap = CollectionUtils.convertMap(deptServiceProvider.getDeptList(
                CollectionUtils.convertSet(employeeList, EmployeeDO::getDeptId)), DeptRespDTO::getId, DeptRespDTO::getName),
                postMap = CollectionUtils.convertMap(postServiceProvider.getPostList(
                        CollectionUtils.convertSet(employeeList, EmployeeDO::getPostId)), PostRespDTO::getId, PostRespDTO::getName),
                postLevelMap = CollectionUtils.convertMap(postLevelServiceProvider.getPostLevelList(
                        CollectionUtils.convertSet(employeeList, EmployeeDO::getPostLevelId)), PostLevelRespDTO::getId, PostLevelRespDTO::getName);
        List<List<String>> data = CollectionUtils.convertList(employeeList, employee -> CollUtil.newArrayList(employee.getName(), employee.getEmployeeNo(),
                deptMap.get(employee.getDeptId()), postMap.get(employee.getPostId()), postLevelMap.getOrDefault(employee.getPostLevelId(), ""),
                EmployeeStatusEnum.getName(employee.getState())));
//        ExcelUtils.write(response, "薪资导入模板.xlsx", "薪资导入模板", head, data, selectMap, null);


        String headText = "说明\n" +
                //
                "1. 表头内容不可修改，表头中带 * 的列为必填项。\n" +
                //
                "2. 出勤、薪资列，负数格式为 -99.12 ，不要使用（）格式化。";

        // 输出 Excel
        final int finalNum = num;
        EasyExcel.write(response.getOutputStream())
                .autoCloseStream(false) // 不要自动关闭，交给 Servlet 自己处理
//                .registerWriteHandler(new WorkbookWriteHandler() {
//                    @Override
//                    public void afterWorkbookDispose(WorkbookWriteHandlerContext context) {
//                        Sheet sheet0 = context.getWriteWorkbookHolder().getWorkbook().getSheetAt(0);
//                        CellRangeAddress cellRangeAddress = new CellRangeAddress(0, 0, 0, finalNum-1);
//                        sheet0.addMergedRegion(cellRangeAddress);
//                        Row firstRow = sheet0.createRow(0);
//                        CellStyle cellStyle = context.getWriteWorkbookHolder().getWorkbook().createCellStyle();
//                        cellStyle.setWrapText(true);
//                        Cell cell = firstRow.createCell(0);
//                        cell.setCellStyle(cellStyle);
//                        cell.setCellValue(headText);
//                    }
//                })
//                .relativeHeadRowIndex(1)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度，自动适配。最大 255 宽度
                .registerWriteHandler(new SelectSheetWriteHandler(selectMap)) // 基于固定 sheet 实现下拉框
                .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
                .sheet("薪资导入模板").head(head).doWrite(data);
        // 设置 header 和 contentType。写在最后的原因是，避免报错时，响应 contentType 已经被修改了
        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("薪资导入模板.xlsx", StandardCharsets.UTF_8.name()));
        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
    }

    @PostMapping("/import")
    @Operation(summary = "导入工资")
    @Parameters({
            @Parameter(name = "file", description = "Excel 文件", required = true),
            @Parameter(name = "year", description = "年份", example = "2000"),
            @Parameter(name = "month", description = "月份", example = "1")
    })
    @PreAuthorize("@ss.hasPermission('hrm:payroll:import')")
    public CommonResult<PayrollImportRespVO> importExcel(@Valid @RequestParam("file") MultipartFile file,
                                                         @RequestParam("year") @Positive(message = "年份格式非法") Integer year,
                                                         @RequestParam("month") @Range(min = 1, max = 12, message = "月份格式非法") Integer month) throws Exception {
        PayrollDO payroll = getCurrentPayroll();
        if(Objects.nonNull(payroll) && (!payroll.getYear().equals(year) || !payroll.getMonth().equals(month))){
            throw exception(PAYROLL_EXISTS, payroll.getMonth());
        }
        // 首先校验传入文件是否为空
        if (file == null) {
            throw exception(DATA_IS_EMPTY);
        }
        // 引入监听器（此处需注意，监听器不可被Spring管理）
        EasyExcelListener readListener = new EasyExcelListener();
        // 开始处理excel
        EasyExcelFactory.read(file.getInputStream(), readListener)
                .sheet(0)
//                .headRowNumber(2) // 表头占两行，包含一行导入说明文件
                .headRowNumber(1) // 表头占两行，包含一行导入说明文件
                .doRead();
        // 获取表头（验空）
        List<Map<Integer, String>> headList = readListener.getHeadList();
        if (CollUtil.isEmpty(headList)) {
            throw exception(DATA_HEADER_IS_EMPTY);
        }
        // 获取表数据(验空)
        List<Map<Integer, String>> dataList = readListener.getDataList();
        if (CollUtil.isEmpty(dataList)) {
            throw exception(DATA_IS_EMPTY);
        }
        //获取头部,取最后一次解析的列头数据
        Map<Integer, String> excelHeadIdxNameMap = headList.get(headList.size() - 1);
        //封装数据体
        List<Map<String, String>> excelDataList = new ArrayList<>();
        for (Map<Integer, String> dataRow : dataList) {
            HashMap<String, String> rowData = new HashMap<>();
            excelHeadIdxNameMap.entrySet().forEach(columnHead -> {
                rowData.put(columnHead.getValue().replace("*", ""), dataRow.get(columnHead.getKey()));
            });
            excelDataList.add(rowData);
        }
        return success(payrollService.importPayrollList(excelDataList, year, month));
    }

    @DeleteMapping("/delete")
    @Operation(summary = "删除薪资记录")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('hrm:payroll:delete')")
    public CommonResult<Boolean> deletePayroll(@RequestParam("id") Long id) {
        payrollService.deletePayroll(id);
        return success(true);
    }
}